package com.adopapa.nezhacloud.redis;

import java.util.Collections;
import java.util.UUID;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;

@Service
public class RedisService {

	private Logger logger = LoggerFactory.getLogger(getClass());

	@Autowired
	private RedisTemplateManager<String, Object> redisTemplateManager;

	/** 锁定资源的key **/
	private static String LOCK_KEY_TEMPLATE = "key_lock_%s";
	/** 持有锁的最长时间 **/
	private final int EXPIRE_TIME = 5000;
	/** 获取不到锁的休眠时间 **/
	private final long SLEEP_TIME = 200;

	public RedisTemplate<String, Object> getRedisTemplate(int dbIndex) {
//		@SuppressWarnings("unchecked")
//		RedisTemplateManager<String, Object> redisTemplateManager = //
//				AppContextUtil.getAppContext().getBean(RedisTemplateManager.class);
		return redisTemplateManager.getRedisTemplate(dbIndex);

	}

	public String lock(String lockName, int dbIndex) throws InterruptedException {
		String lockValue = UUID.randomUUID().toString();
		/**
		 * key value nxxx NX|XX, NX -- Only set the key if it does not already exist. XX
		 * -- Only set the key if it already exist.expx EX|PX, expire time units: EX =
		 * seconds; PX = millisecondstime expire time in the units of expx
		 */
		RedisTemplate<String, Object> redisTemplate = getRedisTemplate(dbIndex);
		final String key = String.format(LOCK_KEY_TEMPLATE, lockName);

		try {
			String result = "";
			RedisCallback<String> callback = (connection) -> {
				JedisCommands commands = (JedisCommands) connection.getNativeConnection();
//                String uuid = UUID.randomUUID().toString();
				SetParams setParams = SetParams.setParams().nx().px(EXPIRE_TIME);
				return commands.set(key, lockValue, setParams);
			};

			while (!"OK".equalsIgnoreCase(result)) {
				Thread.sleep(SLEEP_TIME);
				result = redisTemplate.execute(callback);
			}

		} catch (Exception e) {
			logger.error(ExceptionUtils.getStackTrace(e));
		}

		return lockValue;

	}

	public boolean unlock(String lockName, String lockValue, int dbIndex) {
		final String key = String.format(LOCK_KEY_TEMPLATE, lockName);
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] " //
				+ "then return redis.call('del', KEYS[1]) else return 0 end";
		try {
			RedisCallback<Long> callback = (connection) -> {
				Object nativeConnection = connection.getNativeConnection();
				if (nativeConnection instanceof JedisCluster) {
					return (Long) ((JedisCluster) nativeConnection).eval(script, //
							Collections.singletonList(key), Collections.singletonList(lockValue));
				} else if (nativeConnection instanceof Jedis) {
					return (Long) ((Jedis) nativeConnection).eval(script, //
							Collections.singletonList(key), Collections.singletonList(lockValue));
				}
				return 0L;
			};

			RedisTemplate<String, Object> redisTemplate = getRedisTemplate(dbIndex);
			Long result = redisTemplate.execute(callback);

			return result == 1L;

		} catch (Exception e) {
			logger.error(ExceptionUtils.getStackTrace(e));
		}

		return false;

	}

}
